package main

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
	"time"
)

/////// CONFIG ///////

// Username
var USERNAME = "Username"

// Password
var PASSWORD = "Password"

// Net Auth Server IP, the IP "119.146.175.80" from wyu university
var NASIP = "119.146.175.80"

// Local Net Server IP Prefix, the local IP  "100.2" from wyu university
var LOCALNETPREFIX = "100.2."

var BASEURL = "http://enet.10000.gd.cn:10001/client/"
var LOGINURL = BASEURL + "login"
var CHALLENGEURL = BASEURL + "challenge"
var HEARTBEATURL = "http://enet.10000.gd.cn:8001/hbservice/client/active?"

//
var SECRET = "Eshore!@#"

//
var UA = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"

/////// CONFIG ///////

var IP string
var MAC string
var checkNetClient = &http.Client{Timeout: time.Second * 5}
var httpClient = &http.Client{Timeout: time.Second * 60}
var redirectClient = &http.Client{CheckRedirect: RedirectFunc, Timeout: time.Second * 60}

//getMacAndIpAddress
//return (ip,mac,error)
func getMacAndIpAddress() (string, string, error) {

	interfaces, err := net.Interfaces()
	if err != nil {
		return "", "", err
	}

	for _, i := range interfaces {
		byName, err := net.InterfaceByName(i.Name)
		if err != nil {
			return "", "", err
		}
		addresses, err := byName.Addrs()
		if err != nil {
			return "", "", err
		}
		for _, v := range addresses {
			if v.(*net.IPNet).IP.To4() != nil && strings.Contains(v.(*net.IPNet).IP.To4().String(), LOCALNETPREFIX) {
				fmt.Println(v.(*net.IPNet).IP.To4().String(), ":", i.HardwareAddr.String()+"\n")
				return v.(*net.IPNet).IP.To4().String(), i.HardwareAddr.String(), nil
			}
		}
	}
	return "", "", err
}

func initIpAndMac() {
	IP, MAC, _ = getMacAndIpAddress()
	for IP == "" {
		fmt.Print("当前非学校内网ip,学校内网IP为：100.2.开头\n")
		time.Sleep(3 * time.Second)

		IP, MAC, _ = getMacAndIpAddress()
		if IP != "" {
			break
		}

	}

}

// RedirectFunc 重定向禁止
func RedirectFunc(req *http.Request, via []*http.Request) error {
	fmt.Println(req.RequestURI)
	// 如果返回 非nil 则禁止向下重定向 返回nil 则 一直向下请求 10 次 重定向
	return http.ErrUseLastResponse
}

func getViewState(url string, cookie string) string {
	req, _ := http.NewRequest(http.MethodGet, url, nil)
	if cookie != "" {
		req.Header.Set("Cookie", cookie)
	}
	res, err := checkNetClient.Do(req)
	if err != nil {
		fmt.Println("getViewState , fail: " + err.Error())
		return ""
	}
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	reg,_ := regexp.Compile("javax.faces.ViewState\" value=\".*\"")
	viewState := reg.FindString(string(body))
	viewState = strings.Split(viewState,"value=\"")[1]
	viewState = strings.Trim(viewState,"\"")
	//fmt.Println("getViewState" + string(viewState))

	return viewState
}

func getWebCookie() string {
	requestUrl := "http://172.17.18.2:8080/byod/index.xhtml"

	//javaxViewState:=getPreLoginViewState(requestUrl)
	form := url.Values{
		"usermac":               {""},
		"userurl":               {""},
		"userip":                {""},
		"ssid":                  {""},
		"btn":                   {""},
		"j_id_3_SUBMIT":         {"1"},
		"javax.faces.ViewState": {getViewState(requestUrl,"")},
	}
	requestBody := bytes.NewBuffer([]byte(form.Encode()))
	req, _ := http.NewRequest(http.MethodPost, requestUrl, requestBody)

	req.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
	req.Header.Add("Accept-Encoding", "gzip, deflate")
	req.Header.Add("Accept-Language", "en,zh-CN;q=0.9,zh;q=0.8")
	req.Header.Add("Host", "172.17.18.2:8080")
	req.Header.Add("Connection", "keep-alive")
	req.Header.Add("User-Agent", UA)
	req.Header.Add("Cookie", "serviceType=")
	req.Header.Add("Referer", "http://172.17.18.2:8080/byod/index.xhtml")
	req.Header.Add("Upgrade-Insecure-Requests", "1")
	res, err := redirectClient.Post(requestUrl, "application/x-www-form-urlencoded", requestBody)
	if err != nil {
		return ""
	}
	defer res.Body.Close()
	jsessionId := strings.Split(res.Header.Get("Set-Cookie"), ";")[0]
	fmt.Print(jsessionId + "\n")
	return jsessionId

}

func preWebLogin() {
	requestUrl := "http://172.17.18.2:8080/byod/templatePage/20200318110733542/guestRegister.jsf"
	cookie:=getWebCookie()
	form := url.Values{
		"javax.faces.partial.ajax":    {"true"},
		"javax.faces.source":          {"mainForm:j_id_r"},
		"javax.faces.partial.execute": {"mainForm"},
		"javax.faces.partial.render":  {"mainForm:error mainForm:forResetPwd"},
		"mainForm:j_id_r":             {"mainForm:j_id_r"},
		"mainForm:forResetPwd":        {""},
		"userName":                    {""},
		"userPwd":                     {""},
		"userDynamicPwd":              {""},
		"userDynamicPwdd":             {""},
		"mainForm:userNameLogin":      {USERNAME},
		"mainForm:serviceSuffixLogin": {""},
		"mainForm:passwordLogin":      {base64.StdEncoding.EncodeToString([]byte(PASSWORD))},
		"mainForm:userDynamicPwd":     {""},
		"mainForm:userDynamicPwdd":    {""},
		"mainForm_SUBMIT":             {"1"},
		"javax.faces.ViewState":       {getViewState(requestUrl,cookie)},
	}

	requestBody := bytes.NewBuffer([]byte(form.Encode()))
	//fmt.Print(form.Encode()+"\n\n")
	req, _ := http.NewRequest(http.MethodPost, requestUrl, requestBody)
	req.Header.Add("Host", "172.17.18.2,8080")
	req.Header.Add("Connection", "keep-alive")
	req.Header.Add("Content-Length", "1070")
	req.Header.Add("Accept", "application/xml) text/xml) */*; q=0.01")
	req.Header.Add("X-Requested-With", "XMLHttpRequest")
	req.Header.Add("Faces-Request", "partial/ajax")
	req.Header.Add("User-Agent", UA)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
	req.Header.Add("Origin", "http,//172.17.18.2,8080")
	req.Header.Add("Referer", "http,//172.17.18.2,8080/byod/templatePage/20200318110733542/guestRegister.jsf")
	req.Header.Add("Accept-Encoding", "gzip) deflate")
	req.Header.Add("Accept-Language", "en)zh-CN;q=0.9)zh;q=0.8")
	//req.Header.Add("Cookie", "serviceType=; JSESSIONID=1395582B7C3614182701DD31112EE3D8; oam.Flash.RENDERMAP.TOKEN=v93ce253")
	req.Header.Add("Cookie", cookie)

	res, err := httpClient.Do(req)
	if err != nil {
		return
	}

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	fmt.Println(string(body))

}

func getToken() string {
	strtime := strconv.FormatInt(time.Now().Unix(), 10)
	authenticator := fmt.Sprintf("%x", md5.Sum([]byte(IP+NASIP+MAC+strtime+SECRET)))
	authenticator = strings.ToUpper(authenticator)
	data := make(map[string]interface{})
	data["username"] = USERNAME
	data["clientip"] = IP
	data["nasip"] = NASIP
	data["mac"] = MAC
	data["timestamp"] = strtime
	data["authenticator"] = authenticator
	bytesData, err := json.Marshal(data)
	if err != nil {
		fmt.Println(err.Error())
		return ""
	}

	reader := bytes.NewReader(bytesData)

	req, _ := http.NewRequest(http.MethodPost, CHALLENGEURL, reader)
	req.Header.Set("User-agent", UA)
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	res, err := httpClient.Do(req)
	if err != nil {
		return ""
	}

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)

	bodyString := string(body)
	r := regexp.MustCompile("\"challenge\":\"[0-9a-zA-Z]{4}")
	token := strings.Split(r.FindString(bodyString), "\":\"")[1]
	//fmt.Print(token)
	return token

}

func checkNet() bool {

	res, err := checkNetClient.Get("https://www.baidu.com")
	if err != nil {
		fmt.Println("Net Status , fail: " + err.Error())
		return false
	}
	defer res.Body.Close()
	if res.StatusCode == 200 {
		fmt.Println("Net Status , OK")
		return true
	}

	fmt.Println("Net Status , Fail ")
	return false
}



func heartbeat() string {
	strtime := strconv.FormatInt(time.Now().Unix(), 10)
	authenticator := fmt.Sprintf("%x", md5.Sum([]byte(IP+NASIP+MAC+strtime+SECRET)))
	authenticator = strings.ToUpper(authenticator)
	requestUrl := HEARTBEATURL + "username=" + USERNAME + "&clientip=" + IP + "&nasip=" + NASIP + "&mac=" + MAC + "&timestamp=" + strtime + "&authenticator=" + authenticator
	req, _ := http.NewRequest(http.MethodGet, requestUrl, nil)
	req.Header.Set("User-agent", UA)
	res, err := checkNetClient.Do(req)
	if err != nil {
		return "heart fail"
	}

	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	return string(body)

	//fmt.Print(string(body))
}
func KeepSendingHeartbeat() {
	for true {
		time.Sleep(time.Second * 35)
		//Send a heartbeat packet to keep the client online
		//fmt.Print("持续发送心跳包")
		heartbeat()
	}
}
func login() {
	//wyu university need to web login in first
	preWebLogin()

	strtime := strconv.FormatInt(time.Now().Unix(), 10)
	authenticator := fmt.Sprintf("%x", md5.Sum([]byte(IP+NASIP+MAC+strtime+getToken()+SECRET)))
	authenticator = strings.ToUpper(authenticator)
	data := make(map[string]interface{})
	data["username"] = USERNAME
	data["password"] = PASSWORD
	data["clientip"] = IP
	data["nasip"] = NASIP
	data["mac"] = MAC
	data["timestamp"] = strtime
	data["authenticator"] = authenticator
	bytesData, err := json.Marshal(data)
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	reader := bytes.NewReader(bytesData)

	req, _ := http.NewRequest(http.MethodPost, LOGINURL, reader)
	req.Header.Set("User-agent", UA)
	req.Header.Set("Content-Type", "application/json;charset=UTF-8")
	res, err := httpClient.Do(req)
	if err != nil {
		return
	}
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	fmt.Print(string(body) + "\n")

}
//Infinite loop ensures circuit success
func certainLogin()  {
	for true {
		login()
		time.Sleep(time.Second*10)
		if checkNet() {
			break
		}
	}
}
//Decide whether the class is over
func isQuittingClass()  bool {
	now := time.Now()
	weekday := now.Weekday().String()
	hour := now.Hour()
	minute := now.Minute()

	if weekday =="Saturday"||weekday=="Sunday" {
		return false
	}
	if hour==9&&(minute>50&&minute<=59) {
		return true
	}

	if hour==11&&(minute>45&&minute<=54) {
		return true
	}

	if hour==16&&(minute>20&&minute<=29) {
		return true
	}

	if hour==18&&(minute>05&&minute<=14) {
		return true
	}

	if hour==21&&(minute>10&&minute<=19) {
		return true
	}
	return false
}
func main() {
	isQuittingClass()
	initIpAndMac()
	//Ensure that the first login is successful
	certainLogin()

	//sending heart beat to check whether the connect is dropped
	//var heartbeatResult string
	var lostConnNum = 0
	//async keep sending heartbeat package
	go KeepSendingHeartbeat()

	for true {
		time.Sleep(time.Second * 60)

		if checkNet() {
			lostConnNum = 0
			continue
		}
		if isQuittingClass() {
			certainLogin()
			lostConnNum = 0
			continue
		}
		lostConnNum++
		if lostConnNum == 100 {
			certainLogin()
			
		}

	}
}
